package org.javaboy.shiro.config;

import com.alibaba.druid.pool.DruidDataSource;
import jakarta.servlet.Filter;
import org.apache.shiro.authc.Authenticator;
import org.apache.shiro.authc.pam.AllSuccessfulStrategy;
import org.apache.shiro.authc.pam.AtLeastOneSuccessfulStrategy;
import org.apache.shiro.authc.pam.FirstSuccessfulStrategy;
import org.apache.shiro.authc.pam.ModularRealmAuthenticator;
import org.apache.shiro.cache.CacheManager;
import org.apache.shiro.cache.ehcache.EhCacheManager;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.realm.jdbc.JdbcRealm;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.filter.authc.LogoutFilter;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.javaboy.shiro.realm.MyRealm;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.mapper.MapperScannerConfigurer;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.annotation.*;
import org.springframework.core.env.Environment;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.web.WebApplicationInitializer;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import javax.sql.DataSource;
import java.io.IOException;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * WebConfig 相当于 spring-servlet.xml
 */
@Configuration
@ComponentScan(basePackages = "org.javaboy.shiro")
@EnableWebMvc
@PropertySource("classpath:db.properties")
public class WebConfig implements EnvironmentAware, WebMvcConfigurer {
    private Environment env;

    @Override
    public void setEnvironment(Environment environment) {
        this.env = environment;
    }

    @Bean
    DataSource dataSource() {
        DruidDataSource ds = new DruidDataSource();
        ds.setUsername(env.getProperty("db.username"));
        ds.setPassword(env.getProperty("db.password"));
        ds.setUrl(env.getProperty("db.url"));
        return ds;
    }

    @Bean
    SqlSessionFactoryBean sqlSessionFactoryBean() throws IOException {
        SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
        factoryBean.setDataSource(dataSource());
        factoryBean.setTypeAliasesPackage("org.javaboy.shiro.model");
        factoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:org/javaboy/shiro/mapper/*.xml"));
        return factoryBean;
    }

    @Bean
    MapperScannerConfigurer mapperScannerConfigurer() {
        MapperScannerConfigurer configurer = new MapperScannerConfigurer();
        configurer.setBasePackage("org.javaboy.shiro.mapper");
        configurer.setSqlSessionFactoryBeanName("sqlSessionFactoryBean");
        return configurer;
    }

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/**")
                .addResourceLocations("/");
    }

//    @Bean
    EhCacheManager ehcacheManager() {
        EhCacheManager manager = new EhCacheManager();
        return manager;
    }

    @Bean
    SecurityManager securityManager(MyRealm myRealm) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(myRealm);
        securityManager.setCacheManager(new EhCacheManager());
        //如果存在多个 Realm，可以使用这个方法去配置
        //默认情况下，两个 Realm 只要有一个认证成功，就算成功
//        securityManager.setRealms(Arrays.asList(myRealm,jdbcRealm()));
//        securityManager.setAuthenticator(authenticator(myRealm));
        securityManager.setSessionManager(sessionManager());
        return securityManager;
    }

    @Bean
    Authenticator authenticator(MyRealm myRealm) {
        ModularRealmAuthenticator authenticator = new ModularRealmAuthenticator();
        //FirstSuccessfulStrategy 至少有一个 Realm 认证成功才算成功，但是如果第一个 Realm 认证成功，可以不去执行后面的 Realm
        //AtLeastOneSuccessfulStrategy 至少有一个 Realm 认证成功才算成功，但是如果第一个 Realm 认证成功，还是会去执行后面的 Realm
        FirstSuccessfulStrategy authenticationStrategy = new FirstSuccessfulStrategy();
        //true 表示有一个 Realm 认证成功后，后面的 Realm 就不执行了
        //false 表示有一个 Realm 认证成功后，后面的 Realm 还是继续要去执行
        authenticationStrategy.setStopAfterFirstSuccess(true);
        authenticator.setAuthenticationStrategy(authenticationStrategy);
        //实际上，我们配置给 SecurityManager 的 Realm，最终都是设置给 authenticator 的
        authenticator.setRealms(Arrays.asList(myRealm, jdbcRealm()));
        return authenticator;
    }

    @Bean
    JdbcRealm jdbcRealm() {
        JdbcRealm jdbcRealm = new JdbcRealm();
        //设置数据源
        jdbcRealm.setDataSource(dataSource());
        //自定义用户登录时候的查询 SQL
        jdbcRealm.setAuthenticationQuery("select password from account where name=?");
        return jdbcRealm;
    }

    @Bean
    SessionManager sessionManager() {
        DefaultWebSessionManager manager = new DefaultWebSessionManager();
        //是否开启将 sessionId 重写到地址栏的功能
        manager.setSessionIdUrlRewritingEnabled(false);
        return manager;
    }

    /**
     * Shiro 中的认证功能，主要是通过过滤器来实现的。
     * 这里实际上就是去配置 shiro 中的过滤器的
     *
     * @return
     */
    @Bean
    ShiroFilterFactoryBean shiroFilterFactoryBean(MyRealm myRealm) {
        ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
        bean.setSecurityManager(securityManager(myRealm));
        Map<String, Filter> filters = new LinkedHashMap<>();
        LogoutFilter logoutFilter = new LogoutFilter();
        logoutFilter.setPostOnlyLogout(true);
        filters.put("lg", logoutFilter);
        bean.setFilters(filters);
        //配置登录页面
        bean.setLoginUrl("/login.html");
        //这里要有序
        Map<String, String> urlMaps = new LinkedHashMap<>();
        // /doLogin 这个接口使用 anon 过滤器，即这个接口可以匿名访问
        urlMaps.put("/doLogin", "anon");
        urlMaps.put("/login.html", "anon");
        //这个表示 /hello 这个接口可以通过 用户名密码登录之后访问，也可以通过 RememberMe 自动登录之后访问
        urlMaps.put("/hello", "user");
        urlMaps.put("/logout", "lg");
        //访问 /admin/** 这种格式的地址，需要 admin 角色
//        urlMaps.put("/admin/**", "roles[admin]");
        //访问 /book/insert 这个接口需要 book:create 权限
//        urlMaps.put("/book/create", "perms[book:create]");
        //其他路径，必须登录之后才能访问
        // /admin/hello 则必须是用户名密码登录之后才可以访问
        urlMaps.put("/**", "authc");
        //这个是配置拦截规则的
        bean.setFilterChainDefinitionMap(urlMaps);
        return bean;
    }

    @Bean
    AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(MyRealm myRealm) {
        AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
        advisor.setSecurityManager(securityManager(myRealm));
        return advisor;
    }

    @Bean
    @DependsOn("lifecycleBeanPostProcessor")
    DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
        return new DefaultAdvisorAutoProxyCreator();
    }

    @Bean
    LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }
}
